// This file is a part of Funky C++.
// Copyright (C) 2011  Valentin David
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
#ifndef DELAY_HH
# define DELAY_HH

# include "signal.h"
# include <memory>

namespace funky {

  template <size_t N, typename Signal>
  struct delay_line;


  template <typename Signal>
  struct delay_line<0, Signal> {
  public:
    typedef typename signal_traits<Signal>::sample sample;

  private:
    Signal signal;

  public:
    delay_line(const delay_line&) = default;
    delay_line(delay_line& other)
      : signal(other.signal) {}

    delay_line(delay_line&& other):
      signal(std::move(other.signal)) {}

    delay_line& operator=(const delay_line& other) {
      return *this = delay_line(other);
    }

    delay_line& operator=(delay_line&& other) {
      std::swap(signal, other.signal);
      return *this;
    }

    template <typename S>
    explicit delay_line(S&& s):
      signal(std::forward<S>(s)) {}

    delay_line& operator++() {
      ++signal;
      return *this;
    }

    delay_line operator++(int) {
      delay_line ret{*this};
      ++*this;
      return ret;
    }

    sample operator*() const {
      return *signal;
    }
  };


  template <size_t N, typename Signal>
  struct delay_line {
  public:
    typedef typename signal_traits<Signal>::sample sample;

  private:
    Signal signal;
    bus<sample, N> buffer;
    size_t position;

  public:
    delay_line(const delay_line&) = default;
    delay_line(delay_line& other):
      signal(other.signal),
      buffer(other.buffer),
      position(other.position) {}

    delay_line(delay_line&& other):
      signal(std::move(other.signal)),
      buffer(std::move(other.buffer)),
      position(other.position) {}

    delay_line& operator=(const delay_line& other) {
      return *this = delay_line(other);
    }

    delay_line& operator=(delay_line&& other) {
      std::swap(signal, other.signal);
      std::swap(buffer, other.buffer);
      std::swap(position, other.position);
      return *this;
    }

    template <typename S>
    explicit delay_line(S&& s):
      signal(std::forward<S>(s)),
      position(0u) {
      buffer[position] = *signal;
      position = (position + 1) % N;
    }

    delay_line& operator++() {
      ++signal;
      buffer[position] = *signal;
      position = (position + 1) % N;
      return *this;
    }

    delay_line operator++(int) {
      delay_line ret{*this};
      ++*this;
      return ret;
    }

    const sample& operator*() const {
      return buffer[position];
    }
  };

  template <size_t N, typename Signal>
  struct signal_traits<delay_line<N, Signal> > {
    typedef size_t duration;
    typedef typename delay_line<N, Signal>::sample sample;
  };

  template <size_t N, typename T,
	    typename RT = typename std::decay<T>::type,
	    typename = typename signal_traits<RT>::sample>
  delay_line<N, RT> delay(T&& t) {
    return delay_line<N, RT>{
      std::forward<T>(t)
    };
  }

  template <typename>
  struct ref;

  template <typename Signal>
  struct const_ref {
  private:
    std::shared_ptr<const Signal> signal;

    friend class ref<Signal>;

    explicit const_ref(const std::shared_ptr<const Signal>& signal)
      : signal(signal) {}

  public:
    typedef typename signal_traits<Signal>::sample sample;

    const_ref(const const_ref&) = default;

    const_ref(const_ref&& other)
      : signal(std::move(other.signal)) {}

    const_ref& operator=(const const_ref& other) {
	return *this = const_ref(other);
    }

    const_ref& operator=(const_ref&& other) {
      std::swap(signal, other.signal);
      return *this;
      }

    const_ref& operator++() {
      return *this;
    }

    const_ref& operator++(int) {
      return *this;
    }

      sample operator*() const {
    return **signal;
      }
  };

  template <typename Signal>
    struct ref {
  private:
    std::shared_ptr<Signal> signal;

  public:
    typedef typename signal_traits<Signal>::sample sample;

    template <typename S>
    explicit ref(S&& s): signal(new Signal(std::forward<S>(s))) {}

    const_ref<Signal> get_const_ref() const {
      return const_ref<Signal>{signal};
    }

    ref(ref& other):
      signal(other.signal) {}
    ref(const ref&) = default;
    ref(ref&& other)
      : signal(std::move(other.signal)) {}

    ref& operator=(const ref& other) {
      return *this = ref(other);
    }

    ref& operator=(ref&& other) {
      std::swap(signal, other.signal);
      return *this;
    }

    ref& operator++() {
      ++*signal;
      return *this;
    }

    ref& operator++(int) {
      ref ret{*this};
      ++*this;
      return ret;
    }

    sample operator*() const {
      return **signal;
    }
  };

  template <typename Signal>
  struct signal_traits<ref<Signal> > {
    typedef typename signal_traits<Signal>::duration duration;
    typedef typename ref<Signal>::sample sample;
  };

  template <typename Signal>
  struct signal_traits<const_ref<Signal> > {
    typedef typename signal_traits<Signal>::duration duration;
    typedef typename const_ref<Signal>::sample sample;
  };

  template <size_t...>
  struct tap_delay {
    template <typename T, typename... U>
    static std::tuple<typename std::decay<U>::type...>
    delay(const const_ref<T>&,
	  U&&... u) {
      return std::make_tuple(std::forward<U>(u)...);
    }
  };

  template <size_t First, size_t... N>
  struct tap_delay<First, N...> {
    template <typename T, typename... U,
	      typename Ret =
	      decltype(tap_delay<N...>
		       ::delay(std::declval<const const_ref<T> >(),
			       std::declval<U>()...,
			       std::declval<delay_line<First,
						       const_ref<T> > >()))>
    static Ret
    delay(const const_ref<T>& r, U&&... u)
    {
      return tap_delay<N...>::delay(r, std::forward<U>(u)...,
				    delay_line<First, const_ref<T>>{r});
    }

    template <typename T,
	      typename RT = typename std::decay<T>::type,
	      typename Ret =
	      decltype(tap_delay<N...>
		       ::delay(std::declval<const_ref<RT>>(),
			       std::declval<delay_line<First, ref<RT>>>())),
	      typename = typename signal_traits<RT>::sample>
    static Ret
    delay(T&& t)
    {
      ref<RT> main{std::forward<T>(t)};
      return tap_delay<N...>::delay(main.get_const_ref(),
				    delay_line<First, ref<RT>>{main});
    }
  };
}

#endif
